﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.ComponentModel;
using FinPlusAssembler;
using FinPlusInterfaces;

namespace FinPlusCompProxy
{
    public class DefaultCurve : ConcurrentDictionary<string, CdsSpread>
    {
        private enum Params { LevelUpdate, Refresh, Shock, Instrument, CurveName, NumSpread, RecoveryRate, DayCount, Holidays };

        private IList<IPod> _instruments;
        private QLAnalytics _analytics;
        private IPods _instrumentBuilds;
        private IPod _defaultCurveBuild;
        private BuildEvent _buildEvents;
        private string _curveName, _dayCount, _holidays;
        private int _numSpread, _spreadCount;
        private double _recoveryRate;

        //construct
        public DefaultCurve(QLAnalytics analytics, string curveName, IPod defaultCurveBuild, IPods instrumentBuilds, IList<IPod> instruments, BuildEvent buildEvents)
        {
            _analytics = analytics;
            _curveName = curveName;
            _defaultCurveBuild = defaultCurveBuild;
            _instrumentBuilds = instrumentBuilds;
            _instruments = instruments;
            _recoveryRate = (double)_defaultCurveBuild.Value(Params.RecoveryRate.ToString());
            _dayCount = _defaultCurveBuild[Params.DayCount.ToString()].ToString();
            _holidays = _defaultCurveBuild[Params.Holidays.ToString()].ToString();
            _numSpread = (int)_defaultCurveBuild.Value(Params.NumSpread.ToString());
            _buildEvents = buildEvents;
            Initialise();
        }

        //public
        public void Initialise()
        {
            foreach (var instrument in _instruments)
            {
                if (_buildEvents == BuildEvent.Refresh || _buildEvents == BuildEvent.UpdateAndRefresh)
                    instrument.Get(Params.Refresh.ToString(), string.Empty, Curve_Refresh);

                instrument.Get(Params.LevelUpdate.ToString(), IOC.New<IPod>(), Curve_Update);
                instrument.Get(Params.Shock.ToString(), IOC.New<IPod>(), Curve_Shock);

                var instrumentName = instrument[Params.Instrument.ToString()].ToString();
                var instrumentBuild = _instrumentBuilds.First("Name='Instrument' And Value='" + instrumentName + "'");

                if (instrumentBuild != null)
                {
                    this[instrumentName] = AddSpread(instrumentName, instrument, instrumentBuild);
                    instrument.Get(Params.Refresh.ToString(), string.Empty).Update(instrumentName);
                }
            }
        }

        //private
        private CdsSpread AddSpread(string instrumentName, IPod instrument, IPod instrumentBuild)
        {
            _spreadCount++;
            return new CdsSpread(_analytics, instrumentName, instrument, instrumentBuild, _recoveryRate);
        }

        private void BuildCurve()
        {
            try
            {
                string res;
                lock (_analytics.QL)
                    res = _analytics.QL.DefaultCurve(_analytics.MarketName, _curveName, _analytics.DiscountCurve, _analytics.AsOf, Spreads(_analytics.BuySell), _recoveryRate, _dayCount, _holidays);
            }
            catch (Exception e)
            {
                Level.Error.Log(e.Message, e);
            }

        }

        private bool IsValid()
        {
            if (_numSpread != _spreadCount)
                return false;

            foreach (var spread in this.Values)
                if (Double.IsNaN(spread.Value))
                    return false;

            return true;
        }

        private string Spreads(string buySell)
        {
            return string.Join(",", this.Values.Select(f => f.Build(buySell)).ToArray<string>());
        }

        //events
        private void Curve_Update(object s, PropertyChangedEventArgs a)
        {
            var level = (IPod)((IBean)s).Object;
            var instrument = level[Params.Instrument.ToString()].ToString();

            if (this[instrument].Update(level) && IsValid())
                BuildCurve();
        }

        private void Curve_Refresh(object s, PropertyChangedEventArgs a)
        {
            if (IsValid() && (((IBean)s).ToString().Equals(_curveName)))
                BuildCurve();
        }

        private void Curve_Shock(object s, PropertyChangedEventArgs a)
        {
            var shock = (IPod)((IBean)s).Object;
            var curveName = shock[Params.CurveName.ToString()].ToString();
            if (curveName.Equals(_curveName))
            {
                var instrumentName = shock[Params.Instrument.ToString()].ToString();
                var value = (int)shock.Get(Params.Shock.ToString()).Object;
                this[instrumentName].Shock = value;
                if (value != 0 && IsValid()) BuildCurve();
            }
        }
    }

}
